package cn.zyjblogs.server.user.handler;

import cn.zyjblogs.starter.common.entity.context.BaseContext;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientRegistrationException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.approval.Approval;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
@RequiredArgsConstructor
public class OauthRequestValidator {

    private final TokenStore tokenStore;
    private final JwtAccessTokenConverter jwtAccessTokenConverter;

    public void validateScope(Set<String> requestScopes, Set<String> clientScopes) {

        if (clientScopes != null && !clientScopes.isEmpty()) {
            for (String scope : requestScopes) {
                if (!clientScopes.contains(scope)) {
                    throw new InvalidScopeException("Invalid scope: " + scope, clientScopes);
                }
            }
        }

        if (requestScopes.isEmpty()) {
            throw new InvalidScopeException("Empty scope (either the client or the user is not allowed the requested scopes)");
        }
    }

    public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest, ClientDetails client) {

        String clientId = authorizationRequest.getClientId();
        Collection<String> requestedScopes = authorizationRequest.getScope();
        Set<String> approvedScopes = new HashSet<String>();
        Set<String> validUserApprovedScopes = new HashSet<String>();
        Set<Approval> approvals = new HashSet<Approval>();
        if (client != null) {
            try {
                for (String scope : requestedScopes) {
                    if (client.isAutoApprove(scope)) {
                        approvedScopes.add(scope);
                    }
                }
                if (approvedScopes.containsAll(requestedScopes)) {
                    // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store.
                    Date expiry = computeExpiry(-1);
                    for (String approvedScope : approvedScopes) {
                        approvals.add(new Approval(BaseContext.getUsername(), authorizationRequest.getClientId(),
                                approvedScope, expiry, Approval.ApprovalStatus.APPROVED));
                    }

                    authorizationRequest.setApproved(true);
                    return authorizationRequest;
                }
            } catch (ClientRegistrationException e) {
            }
        }


        // Find the stored approvals for that user and client
        Collection<Approval> userApprovals = getApprovals(clientId);
        // Look at the scopes and see if they have expired
        Date today = new Date();
        for (Approval approval : userApprovals) {
            if (approval.getExpiresAt().after(today)) {
                if (approval.getStatus() == Approval.ApprovalStatus.APPROVED) {
                    validUserApprovedScopes.add(approval.getScope());
                    approvedScopes.add(approval.getScope());
                }
            }
        }

        // If the requested scopes have already been acted upon by the user,
        // this request is approved
        if (validUserApprovedScopes.containsAll(requestedScopes)) {
            approvedScopes.retainAll(requestedScopes);
            // Set only the scopes that have been approved by the user
            authorizationRequest.setScope(approvedScopes);
            authorizationRequest.setApproved(true);
        }

        return authorizationRequest;

    }

    private Date computeExpiry(int approvalExpirySeconds) {
        Calendar expiresAt = Calendar.getInstance();
        if (approvalExpirySeconds == -1) { // use default of 1 month
            expiresAt.add(Calendar.MONTH, 1);
        } else {
            expiresAt.add(Calendar.SECOND, approvalExpirySeconds);
        }
        return expiresAt.getTime();
    }

    private Collection<Approval> getApprovals(String clientId) {
        Collection<Approval> result = new HashSet<Approval>();
        OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(BaseContext.getToken());
        OAuth2Authentication authentication = tokenStore.readAuthentication(BaseContext.getToken());
        if (authentication != null) {
            Date expiresAt = oAuth2AccessToken.getExpiration();
            for (String scope : oAuth2AccessToken.getScope()) {
                result.add(new Approval(BaseContext.getUsername(), clientId, scope, expiresAt, Approval.ApprovalStatus.APPROVED));
            }
        }
        return result;
    }

}
